package org.jspar.place;

import org.jspar.Options;
import org.jspar.Partition;
import org.jspar.model.Module;
import org.jspar.model.Terminal;

public class StringBuilder {

	private static final int MAX_DEPTH = 12;

	private Options configuration;

	public StringBuilder(Options conf) {
		this.configuration = conf;
	}

	public ModuleString buildInitialString(Partition part) {
		ModuleString path = null;
		int[] weight = new int[1];
		int max = 0;
		for (Module m : part) {
			if (m.isRoot()) {
				ModuleString p = longuestPath(part, m, weight);
				if (weight[0] > max) {
					path = p;
					max = weight[0];
				}
			}
		}
		// if the partition contains no root, try from the first node
		if (path == null)
			path = longuestPath(part, part.moduleAt(0), weight);
		return path;
	}

	public ModuleString buildRemainingString(Partition part) {
		ModuleString path = null;
		int[] weight = new int[1];
		int bestWeight = 0;
		int count = 0;
		for (Module m : part) {
			if (! m.hasFlag(Module.PLACED)) {
				ModuleString p = longuestPath(part, m, weight);
				if (weight[0] > bestWeight) {
					path = p;
					bestWeight = weight[0];
					count = path.countConnectionsTo(part);
				} else if (weight[0] == bestWeight) {
					int c = path.countConnectionsTo(part);
					if (c > count) {
						path = p;
						bestWeight = weight[0];
						count = c;
					} else if (betterInheritence(p, path)) {
						path = p;
						bestWeight = weight[0];
						count = c;
					}
				}
			}
		}
		return path;
	}

	private boolean betterInheritence(ModuleString path1, ModuleString path2) {
		
		for (int i = 0; i < path1.length(); ++i) {
			Terminal[] jterms = path1.moduleAt(i).terminals();
			for (int j = 0; j < jterms.length; ++j) {
				if (jterms[j].isOut() || jterms[j].isInOut()) {
					Terminal[] kterms = jterms[j].net().terminals();
					for(int k = 0; k < kterms.length; ++k) {
						if (kterms[k].isIn()
						//&& kterms[k].module() == path1.moduleAt(i) // XXX ??
								&& path2.contains(kterms[k].module())) {
							if (! path1.contains(kterms[k].module())) 
								return true;
						}
					}
				}
			}
		}
		return false;
	}

	public ModuleString longuestPath(Partition p, Module m, int[] weight) {
		p.clearFlags(Module.SEEN);
		return tracePath(p, m, 0, weight);
	}

	private ModuleString tracePath(Partition p,
			Module m, int depth, int[] weight) {

		if (++depth > MAX_DEPTH
				|| m.hasFlag(Module.SEEN)
				|| m.hasFlag(Module.PLACED)) {
			weight[0] = 0;
			return null;
		}

		ModuleString pathSoFar = new ModuleString();
		int lengthSoFar = 0;

		m.setFlag(Module.SEEN);

		for (Terminal iterm : m.terminals()) {
			if (iterm.isOut() || iterm.isInOut()) {
				for (Terminal jterm : iterm.net().terminals()) {
					Module nextMod = jterm.module();
					if (nextMod != m
							&& nextMod.partition() == p
							&& (jterm.isIn() || jterm.isInOut())
							&& ! nextMod.type().isSystemTerminal()
							&& ! nextMod.hasFlag(Module.SEEN)) {
						if (iterm.isOut()) m.setPrimaryOut(iterm);
						if (jterm.isIn()) nextMod.setPrimaryIn(jterm);

						int[] testWeight = new int[1];
						ModuleString testPath = tracePath(p, nextMod,
								depth, testWeight);

						if (configuration.ccSearchLevel > 0
								&& new CrossCoupledDetector().isCrossCoupled(m, nextMod,
									configuration.ccSearchLevel)) {
							pathSoFar = testPath;
							lengthSoFar += testWeight[0];
						} else if (testWeight[0] > lengthSoFar) {
							pathSoFar = testPath;
							lengthSoFar = testWeight[0];
						} else if (testPath != null) {
							testPath.resetTrace();
						}
					}
				}
			}
		}
		weight[0] = lengthSoFar + 1;
		pathSoFar.add(m);
		return pathSoFar;
	}
}
